﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Controls.DataVisualization.Charting;
using System.Globalization;
using System.Windows.Shapes;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusWPFControls
{
    public partial class FinPlusChartProfile : FinPlusControl
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return null; } }
        public override string ModelTest { get { return null; } }
        public override bool IsValid { get; set; }

        private enum DockControls { FinPlusParentService, Exception, Default }
        private IFinPlusComponent _conn;
        private IFinPlusFunctions _funcs;
        private readonly TimeSpan _timeInterval, _repeatInterval;
        private readonly string[] _fields, _titles;
        private readonly int _rowCount;

        //construct
        public FinPlusChartProfile(string fields, string titles, string timeInterval, string repeatInterval, string rowCount)
        {
            InitializeComponent();
            _fields = u.Split(fields);
            _titles =  u.Split(titles);
            _timeInterval = TimeSpan.Parse(timeInterval);
            _repeatInterval = TimeSpan.Parse(repeatInterval);
            _rowCount = int.Parse(rowCount);
        }

        //common control interface
        public override void Dock(object control, string dock)
        {
            try
            {
                switch (u.EnumTryParse<DockControls>(control.GetType().Name, DockControls.Default))
                {
                   case DockControls.FinPlusParentService:
                   case DockControls.Default: AddConnection((FinPlusParentService)control); break;
                }
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        public override void Initialise()
        {

        }

        public override void CommandRouter(IFinPlusFunction func)
        {
            try
            {
                throw new NotImplementedException();
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Warning, e.Message, e);
            }
        }

        public override void Dispose()
        {
            try
            {
                if (_conn != null)
                    _conn.Dispose();
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Warning, e.Message, e);
            }
        }

        //private
        private async void AddConnection(FinPlusParentService conn)
        {
            try
            {
                 _conn = conn;
                 _funcs = conn.GetFunctions();
                 var dataPoints = await Task.Run(() => { return new ProfileDataPoints(_funcs, _fields, _timeInterval, _repeatInterval, _rowCount); });
                 series.DataContext = dataPoints.Values.ToList();
                 chart.Title = ChartFormaters.GetTitle(_funcs.Values.First(), _titles, _timeInterval, dataPoints.Start, dataPoints.End, "dd-MM-yy H:mm");
            }
            catch (Exception e)
            {
                IsValid = log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //private classes
        private class ProfileDataPoints : Dictionary<double, ProfileDataPoint>
        {
            public DateTime Start, End;

            private IFinPlusFunctions _funcs;
            private SortedDictionary<int, ChartDataPoint> _dataPoints;
            private DateTime _periodIntervalEnd, _repeatIntervalEnd;
            private TimeSpan _periodInterval, _repeatInterval;
            private string[] _fields;
            private double _range, _lowest = double.NaN, _highest = double.NaN;
            private int _maxLength, _charInt = 0, _rowCount;
            private char _char = 'A';
               
            public ProfileDataPoints(IFinPlusFunctions funcs, string[] fields, TimeSpan periodInterval, TimeSpan repeatInterval, int rowCount)
            {
                _periodInterval = periodInterval;
                _repeatInterval = repeatInterval;
                _rowCount = rowCount;
                _fields = fields;
                _funcs = funcs;
             
                Initialise();
                GetRanges();
                SetRanges();
            }

            //private
            private void Initialise()
            {
                _dataPoints = new SortedDictionary<int, ChartDataPoint>();

                foreach (var type in _fields)
                {
                    var res = _funcs.Where(string.Format("Name='Info' And Value='{0}'", type));
                    if (res.Count() < 1) continue;

                    foreach (var item in res.First().Value)
                    {
                        if (item.Index < 7) continue;

                        var key = 0;
                        if(!int.TryParse(item.Name, out key)) continue;

                        var dataPoint = _dataPoints.ContainsKey(key) ? _dataPoints[key] : _dataPoints[key] = new ChartDataPoint();
                        dataPoint.Add(item, type);

                        if (type.Equals("CLOSE"))
                        {
                            if(_highest.Equals(double.NaN) || dataPoint.Close > _highest)
                                _highest = dataPoint.Close;

                            if (_lowest.Equals(double.NaN) || dataPoint.Close < _lowest)
                                _lowest = dataPoint.Close;
                        }

                        if (Start.Equals(DateTime.MinValue)) Start = dataPoint.DateTime;
                    }

                    
                    End = _dataPoints.Last().Value.DateTime;
                }

                _range = Math.Round((_highest - _lowest) / _rowCount, 2);
            }

            private void GetRanges()
            {
                var last = Math.Round(_lowest - _range, u.GetNumberOfDp(_range));
                do
                {
                    GetRange(last, last += _range);
                }
                while (last < _highest + _range);
            }

            private void GetRange(double last, double next)
            {
                var dataPoint = new ProfileDataPoint(last, next, _range);
                    this.Add(dataPoint.RangeMid, dataPoint);
            }

            private void SetRanges()
            {
                ChartDataPoint lastDataPoint = null;

                foreach (var dataPoint in _dataPoints.Values)
                {
                    lastDataPoint = dataPoint;
                    SetInterval(dataPoint);
                    SetChar(dataPoint);
                    SetRange(dataPoint);
                }

                if(lastDataPoint != null)
                    SetClose(lastDataPoint);
            }

            private void SetInterval(ChartDataPoint dataPoint)
            {
                            
                if (_periodIntervalEnd.Equals(DateTime.MinValue))
                {
                    _periodIntervalEnd = dataPoint.DateTime + _periodInterval;
                    SetOpen(dataPoint);
                }

                if (dataPoint.DateTime < _periodIntervalEnd) return;

                SetClose(dataPoint);

                foreach (var range in this.Values)
                    range.Fill(_maxLength);

                SetOpen(dataPoint);

                _charInt = 0;
                _char = Convert.ToChar(_charInt + 65);

                _repeatIntervalEnd = _periodIntervalEnd + _repeatInterval;
                _periodIntervalEnd += _periodInterval;
            }

            private void SetChar(ChartDataPoint dataPoint)
            {
                if (_repeatIntervalEnd.Equals(DateTime.MinValue)) _repeatIntervalEnd = dataPoint.DateTime + _repeatInterval;

                if (dataPoint.DateTime < _repeatIntervalEnd) return;

                _charInt = _charInt % 26;
                _char = Convert.ToChar(_charInt + 65);
                _charInt++;

                _repeatIntervalEnd += _repeatInterval;
            }

            private void SetRange(ChartDataPoint dataPoint)
            {
                foreach (var range in this.Values)
                {
                    if (range.RangeMin <= dataPoint.High && range.RangeMax >= dataPoint.Low)
                    {
                        range.Add(dataPoint, _char);
                        
                        if (range.Length > _maxLength) 
                            _maxLength = range.Length;
                    }
                }
            }

            private void SetClose(ChartDataPoint dataPoint)
            {
                foreach (var range in this.Values)
                {
                    if (range.RangeMin <= dataPoint.Close && range.RangeMax >= dataPoint.Close)
                    {
                        range.Add(dataPoint, '.');
                        if (range.Length > _maxLength)
                            _maxLength = range.Length;
                        return;
                    }
                }
            }

            private void SetOpen(ChartDataPoint dataPoint)
            {
                foreach (var range in this.Values)
                {
                    if (range.RangeMin <= dataPoint.Open && range.RangeMax >= dataPoint.Open)
                    {
                        range.Add(dataPoint, '>');
                        if (range.Length > _maxLength)
                            _maxLength = range.Length;
                        return;
                    }
                }
            }
        }

        private class ProfileDataPoint 
        {
            public int Period { get; set; }
            public int Count { get; set; }
            public int Length { get { return _text.Length; } }
            public string AlphaText { get { return _text.ToString();  } }
            public double RangeMid { get; set; }
            public double RangeMin { get; set; }
            public double RangeMax { get; set; }
            public double Range { get; set; }

            private StringBuilder _text; 

            public ProfileDataPoint(double intervalLow, double intervalHigh, double range) 
            {
                RangeMin = Math.Round(intervalLow, 6);
                RangeMax = Math.Round(intervalHigh, 6);
                Range = range;
                RangeMid = Math.Round((intervalLow + intervalHigh) / 2, u.GetNumberOfDp(range) + 2 );
                Period = 1;
                _text = new StringBuilder();
            }

            public void Add(ChartDataPoint dataPoint, char c)
            {
                _text.Append(c);
                Count++;
            }

            public void Fill(int maxLength)
            {
                if (Length < maxLength)
                    _text.Append(new String(' ', maxLength - Length));
                _text.Append('*');
            }
        }
    }

}
